/**
 * @file FuncPattern.cc
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The implementation of pattern class.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#include "FuncPattern.hh"

namespace ifsim {

FuncPattern::FuncPattern()
{
  ;
}

FuncPattern::~FuncPattern()
{
  ;
}

FuncPatternManager::FuncPatternManager()
{
  _netlistManager = NetlistManager::getOrCreateNetlistManager();
}

FuncPatternManager::~FuncPatternManager()
{
  ;
}

/**
 * @brief Read functional pattern.
 */
bool FuncPatternManager::readFuncPattern(std::string vcd_file)
{
  getNetlistInfo();

  if (parseVCDfile(vcd_file) && mapVCDsignalsToGid()) {
    getToggledStiAndResponsePerFrame();
  } else {
    return false;
  }

  return true;
}

/**
 * @brief Get netlist info.
 */
void FuncPatternManager::getNetlistInfo()
{
  _gates = &(_netlistManager->simNetlist().nodes()[0]);
  _numPIs = _netlistManager->simNetlist().numPIs();
  _numPOs = _netlistManager->simNetlist().numPOs();
  _firstPO = _netlistManager->simNetlist().firstNodePO();
}

/**
 * @brief Parser VCD file.
 */
bool FuncPatternManager::parseVCDfile(std::string file)
{
  VCDFileParser parser;
  _trace = parser.parse_file(file);
  if (nullptr == _trace) {
    return false;
  }
  // only the signals at the top module scope will be dumped
  for (VCDScope* scope : *_trace->get_scopes()) {
    if (!(scope->signals).empty()) {
      _signals = &scope->signals;
      break;
    }
  }
  if (nullptr == _signals) {
    return false;
  }
  return true;
}

/**
 * @brief Map VCD signals to gid.
 */
bool FuncPatternManager::mapVCDsignalsToGid()
{
  int num_signals = _signals->size();

  for (int signal_i = 0; signal_i < num_signals; ++signal_i) {
    VCDSignal* signal = (*_signals)[signal_i];

    if (signal->rindex != -1) {  // multi bits signal
      std::vector<int> tmp_pi_vec, tmp_po_vec;
      for (int bit_i = signal->rindex; bit_i <= signal->lindex; ++bit_i) {
        std::string signal_name(signal->reference);
        int pi_gid = -1, po_gid = -1;  // may be inout node, have one PI and one PO

        signal_name += "[";
        signal_name += std::to_string(bit_i);
        signal_name += "]";
        if (mapOneVCDsignalToGid(signal_name, pi_gid, po_gid)) {
          if (pi_gid != -1) {
            tmp_pi_vec.push_back(pi_gid);
          }
          if (po_gid != -1) {
            tmp_po_vec.push_back(po_gid);
          }
        } else {
          return false;
        }
      }
      if (!tmp_pi_vec.empty()) {
        _gids.push_back(tmp_pi_vec);
      }
      if (!tmp_po_vec.empty()) {
        _gids.push_back(tmp_po_vec);
      }

    } else {  // one bit signal
      std::string signal_name(signal->reference);
      int pi_gid = -1, po_gid = -1;  // may be inout node, have one PI and one PO

      if (signal->lindex != -1) {
        signal_name += "[";
        signal_name += std::to_string(signal->lindex);
        signal_name += "]";
      }

      if (mapOneVCDsignalToGid(signal_name, pi_gid, po_gid)) {
        if (pi_gid != -1) {
          std::vector<int> tmp_vec(1, pi_gid);
          _gids.push_back(tmp_vec);
        }
        if (po_gid != -1) {
          std::vector<int> tmp_vec(1, po_gid);
          _gids.push_back(tmp_vec);
        }
      } else {
        return false;
      }
    }
  }
  return true;
}

/**
 * @brief Map one VCD signal to gid.
 */
bool FuncPatternManager::mapOneVCDsignalToGid(std::string signal_name, int& pi_gid, int& po_gid)
{
  for (int i = 0; i < _numPIs; ++i) {  // find in PI
    if (_gates[i].stdInstName() == signal_name) {
      pi_gid = i;
      break;
    }
  }
  for (int i = 0; i < _numPOs; ++i) {  // find in PO
    int po_id = _firstPO + i;
    if (_gates[po_id].stdInstName() == signal_name) {
      po_gid = po_id;
      break;
    }
  }
  if (pi_gid == -1 && po_gid == -1) {
    LOG_ERROR << "cannot find signal " << signal_name << " in both PIs and POs";
    return false;
  }
  return true;
}

/**
 * @brief Toggled stimuli per frame.
 */
void FuncPatternManager::getToggledStiAndResponsePerFrame()
{
  std::vector<std::vector<Value>> old_sti;        // record last sti of each PI
  std::vector<int> last_it(_signals->size(), 0);  // record it of last time stamp getting val
  uint sti_tail = 0;
  Value parallel_X, parallel_Z;

  parallel_X.setParallelX();
  parallel_Z.setParallelZ();
  _toggledSti.clear();
  std::vector<VCDTime>* timeStamps = _trace->get_timestamps();
  if (timeStamps->empty()) {  // no signal toggle
    LOG_ERROR << "the signals in the VCD file have no value";
    return;
  }

  // time 0
  std::vector<ToggledGate> sti_0;
  getFrame0ToggledStiAndResponse(sti_0, old_sti);
  _toggledSti.insert(_toggledSti.end(), sti_0.cbegin(), sti_0.cend());
  _stiFrameBegin.push_back(sti_tail);
  sti_tail += sti_0.size();

  // time > 0
  for (uint t = 1; t < timeStamps->size(); t++) {
    std::vector<ToggledGate> sti;
    std::vector<Value> po_vals(_response[_response.size() - 1]);

    for (uint signal_i = 0; signal_i < _signals->size(); ++signal_i) {
      VCDSignal* signal = (*_signals)[signal_i];
      VCDValue* vcd_val = nullptr;
      vcd_val = _trace->get_signal_value_at(signal->hash, (*timeStamps)[t], false, last_it[signal_i]);

      if (signal->size == 1) {  // one bit signal
        u_char func_val = vcd_val->get_value_bit();
        Value pat_val;
        convertVCDValToPatVal(func_val, pat_val);
        int gid = _gids[signal_i][0];
        if (gid < _numPIs) {  // PI
          if (old_sti[signal_i][0] != pat_val) {
            setToggledGate(sti, gid, pat_val);  // only record toggled input
            old_sti[signal_i][0] = pat_val;
          }
        } else {  // PO
          int po_i = gid - _firstPO;
          if (pat_val == parallel_Z) {
            pat_val = parallel_X;
          }
          po_vals[po_i] = pat_val;
        }

      } else {  // multi bits signal
        VCDBitVector func_vals = vcd_val->get_value_vector();
        int num_bits = _gids[signal_i].size();
        for (int bit_i = 0; bit_i < num_bits; ++bit_i) {
          u_char func_val = func_vals[num_bits - bit_i - 1];
          Value pat_val;
          convertVCDValToPatVal(func_val, pat_val);
          int gid = _gids[signal_i][bit_i];
          if (gid < _numPIs) {  // PI
            if (old_sti[signal_i][bit_i] != pat_val) {
              setToggledGate(sti, gid, pat_val);  // only record toggled input
              old_sti[signal_i][bit_i] = pat_val;
            }
          } else {  // PO
            int po_i = gid - _firstPO;
            if (pat_val == parallel_Z) {
              pat_val = parallel_X;
            }
            po_vals[po_i] = pat_val;
          }
        }
      }
    }
    if (!sti.empty()) {  // skip frame with no PI toggled
      _toggledSti.insert(_toggledSti.end(), sti.cbegin(), sti.cend());
      _stiFrameBegin.push_back(sti_tail);
      sti_tail += sti.size();
      _response.push_back(po_vals);
    }
  }
  _stiFrameBegin.push_back(sti_tail);
}

/**
 * @brief At frame0 (time = 0), all the PIs need to be recorded.
 * @param first_sti : store the initial values for later comparison
 */
void FuncPatternManager::getFrame0ToggledStiAndResponse(std::vector<ToggledGate>& sti, std::vector<std::vector<Value>>& first_sti)
{
  Value parallel_X, parallel_Z;
  parallel_X.setParallelX();
  parallel_Z.setParallelZ();
  std::vector<Value> po_vals(_numPOs, parallel_X);

  for (uint signal_i = 0; signal_i < _signals->size(); ++signal_i) {
    VCDSignalValues* signal_vals = _trace->get_signal_values((*_signals)[signal_i]->hash);
    uint sv_size = signal_vals->size();

    if (sv_size == 0) {  // no val
      continue;

    } else {
      VCDTimedValue& signal_val = (*signal_vals)[0];
      if (_gids[signal_i].size() == 1) {  // one bit signal
        int gid = _gids[signal_i][0];
        u_char func_val = signal_val.value.get_value_bit();
        Value pat_val;
        convertVCDValToPatVal(func_val, pat_val);
        if (gid < _numPIs) {  // PI, store toggled stimuli
          setToggledGate(sti, gid, pat_val);
          std::vector<Value> tmp_vec(1, pat_val);
          first_sti.push_back(tmp_vec);
        } else {  // PO, store response
          int po_i = gid - _firstPO;
          if (pat_val == parallel_Z) {
            pat_val = parallel_X;
          }
          po_vals[po_i] = pat_val;
        }

      } else {  // multi bits signal
        VCDBitVector func_vals = signal_val.value.get_value_vector();
        std::vector<Value> tmp_vec;
        int num_bits = _gids[signal_i].size();
        for (int bit_i = 0; bit_i < num_bits; ++bit_i) {
          int gid = _gids[signal_i][bit_i];
          u_char func_val = func_vals[num_bits - bit_i - 1];
          Value pat_val;
          convertVCDValToPatVal(func_val, pat_val);
          if (gid < _numPIs) {  // PI, store toggled stimuli
            setToggledGate(sti, gid, pat_val);
            tmp_vec.push_back(pat_val);
          } else {  // PO, store response
            int po_i = gid - _firstPO;
            if (pat_val == parallel_Z) {
              pat_val = parallel_X;
            }
            po_vals[po_i] = pat_val;
          }
        }
        if (!tmp_vec.empty()) {
          first_sti.push_back(tmp_vec);
        }
      }
    }
  }
  _response.push_back(po_vals);
}

/**
 * @brief Convert VCD val to pattern val.
 */
void FuncPatternManager::convertVCDValToPatVal(u_char vcd_val, Value& pat_val)
{
  if (vcd_val == VCD_0) {
    pat_val.setParallel0();
  } else if (vcd_val == VCD_1) {
    pat_val.setParallel1();
  } else if (vcd_val == VCD_X) {
    pat_val.setParallelX();
  } else {
    pat_val.setParallelZ();
  }
}

}  // namespace ifsim